﻿using System;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Office.Server.Search.Query;

namespace Trentacular.Office.Server.Search.FacetedSearch.Providers
{
    public class ManagedPropertyFacetProvider : BaseFacetProvider
    {
        /// <summary>
        /// The key name of the managed property name configuration property.
        /// </summary>
        public const string ManagedPropertyNameKey = "ManagedPropertyName";

        /// <summary>
        /// The key name of the exclusion expression configuration property.
        /// </summary>
        public const string ExclusionExpressionKey = "ExclusionExpression";

        private string _managedPropertyName;

        /// <summary>
        /// Gets the name of the managed property.
        /// </summary>
        /// <value>The name of the managed property.</value>
        protected virtual string ManagedPropertyName
        {
            get
            {
                if (_managedPropertyName == null)
                    _managedPropertyName = LoadManagedPropertyNameFromConfiguration();
                return _managedPropertyName;
            }
        }

        protected string LoadManagedPropertyNameFromConfiguration()
        {
            var managedPropertyName = Configuration.Properties[ManagedPropertyNameKey];

            if (string.IsNullOrEmpty(managedPropertyName))
                throw new ArgumentNullException(ManagedPropertyNameKey);

            return managedPropertyName;
        }

        private int fieldIndex;
        private Type fieldType;
        private IManagedPropertyFacetBuilder facetBuilder;
        private Regex exclusionExpression;

        /// <summary>
        /// Gets the managed properties that need to be included in the facet query.
        /// </summary>
        /// <value>The managed properties to be included in the facet query.</value>
        public override string[] ManagedPropertiesToQuery
        {
            get { return new[] { this.ManagedPropertyName }; }
        }

        /// <summary>
        /// Initializes the facet provider before any rows have been iterated.
        /// </summary>
        /// <param name="resultTable">The result table.</param>
        public override void Initialize(ResultTable resultTable)
        {
            fieldIndex = resultTable.GetOrdinal(this.ManagedPropertyName);
            fieldType = resultTable.GetFieldType(fieldIndex);
            
            facetBuilder = InstantiateFacetBuilder(fieldType);
            facetBuilder.FacetedSearchContext = this.FacetedSearchContext;
            facetBuilder.Configuration = Configuration;
            facetBuilder.ManagedPropertyName = this.ManagedPropertyName;

            // Create the Exclusion Regular Expression
            var exclusionExpressionString =
                Configuration.Properties[ExclusionExpressionKey];

            if (!string.IsNullOrEmpty(exclusionExpressionString))
                exclusionExpression = new Regex(exclusionExpressionString);
        }

        /// <summary>
        /// Processes a single row of the result table.
        /// </summary>
        /// <param name="resultTable">The result table.</param>
        public override void ProcessRow(ResultTable resultTable)
        {
            // If DBNull, lets get out of here
            if (resultTable.IsDBNull(fieldIndex))
                return;

            var fieldValue = resultTable.GetValue(fieldIndex);
            var keys = facetBuilder.GetKeys(fieldValue);
            foreach (var key in keys)
            {
                // First check to see if the key matches the exclusion expression
                if (exclusionExpression != null && exclusionExpression.IsMatch(key))
                    continue;

                if (this.FacetTable.ContainsKey(key))
                {
                    this.FacetTable[key].Count++;
                    return;
                }

                var facet = facetBuilder.CreateFacet(key, fieldValue);
                facet.Count = 1;

                this.FacetTable[key] = facet;
            }
        }

        /// <summary>
        /// Called after all rows have been processed and returns the generated facets.
        /// If there are more facets than the specified MaxFacetsToDisplay, then only
        /// the facets with the highest counts are returned.
        /// </summary>
        /// <returns>A list of all generated facets.</returns>
        public override FacetGroup GetFacets()
        {
            if (facetBuilder == null)
                throw new InvalidOperationException(
                    "The facet provider has not been initialized"
                    );

            // Select the facets with the highest counts
            var keyedFacets = this.FacetTable.OrderByDescending(p => p.Value.Count)
                .Take(Configuration.MaxFacetsToDisplay);

            // Sort by key if that behavior is specified by
            // the particular facet builder
            if (facetBuilder.SortByKey)
                keyedFacets = keyedFacets.OrderBy(p => p.Key);

            return new FacetGroup(
                this.Configuration,
                keyedFacets.Select(p => p.Value).ToList()
                );
        }

        /// <summary>
        /// Instantiates the appropriate facet builder based on the given field type.
        /// </summary>
        /// <param name="fieldType">Type of the field.</param>
        /// <returns></returns>
        protected virtual IManagedPropertyFacetBuilder InstantiateFacetBuilder(
            Type fieldType)
        {
            if (fieldType == typeof(string))
                return new StringManagedPropertyFacetBuilder();

            if (fieldType == typeof(DateTime))
                return new DateTimeManagedPropertyFacetBuilder();

            if (fieldType == typeof(string[]))
                return new StringArrayManagedPropertyFacetBuilder();

            throw new NotSupportedException(
                string.Format(
                    "The field type '{0}' is not supported.",
                    fieldType.FullName
                    )
                );
        }
    }
}
